iT邦幫忙

2024 iThome 鐵人賽

DAY 1
2
Python

為你自己讀 CPython 原始碼系列 第 1

Day 1 - 來讀 CPython 原始碼!

  • 分享至 

  • xImage
  •  

本文同步刊載於 「為你自己學 Python - 來讀 CPython 原始碼

來讀 CPython 原始碼!

為你自己學 Python

什麼是 CPython?

如果這是你第一次聽到 CPython 這個名字,也許你會以為這又是一款新的 Python 實作品。其實 CPython 不是什麼新玩具,它就是大家常在用的 Python 本尊。因為 Python 是使用 C 語言開發的,所以通常講到它的時候,沒特別聲明的話指的就是 CPython。

為什麼要讀原始碼?

為什麼喔?沒有為什麼,就好玩而已,好玩很重要!

看到這裡,如果各位預期透過閱讀 CPython 讓你的撰寫 Python 的功力大增,那很可能要失望了。閱讀 CPython 原始碼的確會增進 Python 的功力,但不多。CPython 的原始碼大部份都是 C 語言寫的,所以硬要說的話,閱讀 CPython 原始碼比較能增進你的 C 語言的功力。

所以,如果你只是想要學習 Python 語法的話,只要看「為你自己學 Python」的入門應用篇就很足夠了。閱讀 CPython 的原始碼,最主要是可以了解 Python 背後的黑魔法,例如 Python 的物件是什麼東西、模組是怎麼載入的、Python 的記憶體管理之類的

從哪裡開始?

取得原始碼

雖然整個 CPython 專案都可以直接在 GitHub 網站上看的到,但追原始碼這件事,建議還是把整個 CPython 的原始碼拉一份到自己電腦裡慢慢研究比較有效率。如果不知道怎麼使用 Git 下載原始碼的話,可以參考我另一本書「為你自己學 Git」的教學。

因為 CPython 的原始碼可能有時候會有點複雜,所以我可能會視情況省略部份程式碼,例如原本可能像這樣:

// 檔案:Include/object.h

struct _object {
    _PyObject_HEAD_EXTRA

#if (defined(__GNUC__) || defined(__clang__)) \
        && !(defined __STDC_VERSION__ && __STDC_VERSION__ >= 201112L)
    // On C99 and older, anonymous union is a GCC and clang extension
    __extension__
#endif
#ifdef _MSC_VER
    // Ignore MSC warning C4201: "nonstandard extension used:
    // nameless struct/union"
    __pragma(warning(push))
    __pragma(warning(disable: 4201))
#endif
    union {
       Py_ssize_t ob_refcnt;
#if SIZEOF_VOID_P > 4
       PY_UINT32_T ob_refcnt_split[2];
#endif
    };
#ifdef _MSC_VER
    __pragma(warning(pop))
#endif

    PyTypeObject *ob_type;
};

中間有好幾個 #ifdef 的條件編譯指令,這些大部份時候可能跟我們要講的重點比較沒關係,所以我會視情況把它拿掉,原本的程式碼看起來會變成這樣:

struct _object {
    _PyObject_HEAD_EXTRA

    union {
       Py_ssize_t ob_refcnt;
    };

    PyTypeObject *ob_type;
};

別誤會,並不是那些拿掉的東西不重要,而是把這些判斷拿掉比較能讓重點更聚焦。

開發工具

不少開發工具都能用來追原始碼,例如 Vim、Visual Studio Code(以下簡稱 VSCode)這樣的文字編輯器,或是 Visual Studio 之類功能完整的 IDE,這些工具都有很好的原始碼閱讀功能,像是很快的跳到函數或常數的定義或是搜尋關鍵字等等,都能幫助我們更有效率的閱讀原始碼。

雖然各位從文字可能看不出來,但在這個系列文章中,我會使用 VSCode 來追原始碼,如果你還沒有安裝的話,可以到 VSCode 官網 下載安裝。

除了 VSCode 之外,最近我有個新歡叫做 Zed,這是個用 Rust 開發的文字編輯器,效能比 VSCode 好很多,特別遇到檔案比較多或檔案比較大的時候,效能表現是肉眼可見的那種等級的差別,也推薦給各位。

不會 C 語言看的懂嗎?

先說結論:就算沒有 100% 看懂也沒關係,不求甚解也無妨,過程還是能學到不少東西。

學習並不是一直線的,不需要完全看懂才能有所收穫,有時候只是看看原始碼的結構、函數的呼叫、變數的命名就能有所收穫。就像拼圖一樣,不一定要把每塊拼得完美,只要把整體的輪廓拼出來,就能看到一幅完整的畫。

別擔心,我也不會 C 語言,幸運的是這個世代有 AI/GPT 可以抱大腿,遇到看不懂的,就丟給 GPT 幫忙解釋,所以我也是邊看原始碼的過程一邊學 C 語言,就算是瞎子摸象,摸久後也能摸出一些東西來。

而且整個 CPython 專案現在也不全然都是用 C 語言寫的,裡面有些模組是用 Python 寫的,如果你原本就會一些 Python 的話,這些應該會比較友善一些。

不過如果你是完全的新手,沒寫過任何一種程式語言,那可能會有點難跟上,因此我會預期你對程式語言有一些基本的認識,不一定要是 Python,會其它程式語言也可以,知道什麼是變數、函數、迴圈、流程控制之類的基本觀念應該差不多就夠了。

因為在閱讀原始碼的過程中我可能會試著用 printf() 函數印一些資料出來看看,所以可能也得學一下 C 語言的 Hello World 怎麼寫。所以,接下來我們就動手來寫個 C 語言版的 Hello World 吧!

Hello World

哇!怎麼才第一章就要動手寫程式啦!別怕,在 C 語言寫 Hello World 沒想像中的難,就跟平常各位在 Python 要寫 Hello World 差沒多少。在 Python 你大概會建立一個 .py 檔一樣,在 C 語言就新增一個 .c 的檔案就好,這裡我就讓它叫做 hello.c,你想換成別的名字也沒可以。檔案內容如下:

// 檔案:hello.c

#include <stdio.h>

int main()
{
  printf("Hello World\n");
  return 0;
}
  • 因為待會要使用 printf() 函數來印點東西出來,這需要引入 C 語言的函式庫 <stdio.h>,這裡包含了執行 printf() 函數所需要的宣告。
  • main() 是整個程式的主要進入點,每個 C 程式都規定要有一個 main 函數,待會執行這個程式的時候會從這裡開始執行。
  • printf() 就是這整段程式碼的主角啦,由它把 Hello World 給印出來。
  • 最後的 return 0 表示程式結束,在慣例上如果回傳一個非 0 的值通常表示執行有問題,這裡我就回傳一個 0 表示正常執行完畢。

不像 Python 或 JavaScript 可以直接執行 .py.js 檔,C 語言需要先把 .c 檔進行編譯成可執行檔才能執行。

C 語言的編譯器有好幾款,而且不同的作業系統各有不同的編譯器,像是 clang 或是 gcc,在 Windows 上的話可找看看 Visual Studio(注意,不是 VSCode 喔), 或是安裝 WSL(Windows Subsystem for Linux)來使用 Linux 上的編譯器,更多詳細資訊可參考 Python Developer's Guide 網站的介紹。同時,這個網站上也有很多關於 CPython 開發的資訊,有興趣的話可以看看。

我的電腦是 macOS,這裡我用 clang 對我剛才寫的 hello.c 進行編譯:

$ clang hello.c -o helloworld

後面的 -o 是指要把原本的 hello.c 編譯並輸出成 helloworld 執行檔,如果編譯過程沒出錯的話,應該會在當前的目錄看到這個檔案,然後就可以執行了:

$ ./helloworld
Hello World

恭喜!你的 C 語言初體驗就完成了!

下個章節,我們就一起來看一下 CPython 的專案結構,順便在你的電腦編譯 CPython 原始碼!

本文同步刊載於 「為你自己學 Python - 來讀 CPython 原始碼


下一篇
Day 2 - CPython 專案簡介
系列文
為你自己讀 CPython 原始碼4
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言